#include "theater.h"
#include "synch.h"
#include "system.h"
#include<stdlib.h>

unsigned int numCustomersInGroup[100000];			    //Used to keep track of how many customers are in each group

int find_shortest_line(unsigned int line[], unsigned int linesState[],int arraysize)
{
    //DBGPRINT(DEBUG_TRACE,"find_shortest_line");
    int shortest=-1;
    //see if any ticketClerk not busy
    for (int i=0; i<arraysize; i++)
    {
        if (line[i] == 0&&OFF_WORK!=linesState[i])		// Found a ticket Clerk that is not busy // I don't have to wait
        {
            shortest = i;
            break;
        }
    }
    // DBGPRINT(DEBUG_TRACE,"find_shortest_line");
    // If I don't have a clerk, choose the shortest line
    if (shortest == -1)
    {
        int shortestTLine = 0;
        int shortestTLineLength=0;
        shortestTLine=0;
        shortestTLineLength = line[0];
        for (int i=0; i<arraysize; i++)
        {
            if(OFF_WORK!=linesState[i])
            {
                if (shortestTLineLength>line[i])// and check ticketClerkState
                {
                    shortestTLine = i;
                    shortestTLineLength = line[i];
                }
            }
        }
        //i have the shortest line
        shortest = shortestTLine;
    }
    //DBGPRINT(DEBUG_TRACE,"find_shortest_line");
    return shortest;

}

void divideIntoGroups( )
{
    int customersToAddToGroups = numCustomers;
    int groupSize = 0;
    while(customersToAddToGroups > 0)
    {
		if(customersToAddToGroups < 5) //Don't add more customers to a group than remain free
			groupSize = rand()%customersToAddToGroups + 1;
		else
			groupSize = rand()%5 + 1; //Max group size is 5
		customersToAddToGroups -= groupSize;
		numCustomersInGroup[numGroups] = groupSize;
		numGroups++;
    }
	numGroupsRemaining = numGroups;
}

void generatCustomer( )
{
    int nextCustomerNumber=0;
    for ( int i=0; i<numGroups; i++ )
    {
        //Generate the size of the group, not counting head customer
        int groupSize = numCustomersInGroup[i];
        //Create and fork the head customer thread
        //The head customer of a group needs a group number, group size,
        //and a customer number. I will store all 3 values in a struct pointer v and pass
        //it as the second argument to Fork
        CustomerData *current = new CustomerData;
        current->customerNumber = nextCustomerNumber;
        nextCustomerNumber++; //don't need a lock as only this main thread does this
        current->groupNumber = i;
        current->groupSize = groupSize;
        DBGPRINT(DEBUG_TRACE, "we generate a group :size %d, number %d, headercustomer %d\n", current->groupSize,current->groupNumber,current->customerNumber);
        Thread *t = new Thread("Head Customer");
        t->Fork(headCustomer_start,(int)current);
		printf("Customer [%d] in Group [%d] has entered the movie theater\n", current->customerNumber, current->groupNumber);
        //Now make all the regular Customers for this group
        for ( int j=0; j<(groupSize-1); j++ )
        {
            CustomerData *current = new CustomerData;
            current->customerNumber = nextCustomerNumber;
            nextCustomerNumber++;
            current->groupNumber = i;
            Thread *t = new Thread("Customer");
            t->Fork(regularCustomer_startWork,(int) current );
			printf("Customer [%d] in Group [%d] has entered the movie theater\n", current->customerNumber, current->groupNumber);
        }
    }
    DBGPRINT(DEBUG_TRACE, "we got %d groups, and % d customers\n",numGroups,nextCustomerNumber);
}

//the manager told the ticketTaker to work
void headCustomer_start(int cus)
{

    CustomerData *current=(CustomerData *)cus;
    //step a: buy the ticket
    //step 1: choose the shortest line
find_ticketclerk:
    ticketClerkLineLock->Acquire();
    int myTicketClerk=find_shortest_line(ticketclrk_cus_count,ticketclrk_off,numTicketClerks);
    DBGPRINT(DEBUG_INFO, "headcustomer  %d get in the line %d\n", current->customerNumber,myTicketClerk);
    //get in that line
    ticketclrk_cus_count [myTicketClerk]++;
    if(ticketclrk_cus_count [myTicketClerk]>1)//I need to wait if there is more than me
    {
        DBGPRINT(DEBUG_INFO, "headcustomer  %d wait in the line %d\n",current->customerNumber,myTicketClerk);
		printf("Customer [%d] in Group [%d] is getting in TicketClerk line [%d]\n", current->customerNumber, current->groupNumber, myTicketClerk);
        ticketclerkline_cv[myTicketClerk]->Wait(ticketClerkLineLock);
        //right here we need check the clerk is off or not, if off, we need to find a new one
        if(OFF_WORK==ticketclrk_off[myTicketClerk]||ADD_NEWLINE==ticketclrk_addNewline[myTicketClerk])
        {
            DBGPRINT(DEBUG_INFO, "headcustomer  %d is told the line %d is off, need to change\n", current->customerNumber,myTicketClerk);
			printf("Customer [%d] in Group [%d] sees TicketClerk [%d] is on break.\n", current->customerNumber, current->groupNumber, myTicketClerk);
            ticketClerkLineLock->Release();
            goto find_ticketclerk;
        }
    }
    ticketClerkLineLock->Release();
    //step 2: set the ticketclerk
    //step 3: tell the ticketclerk how many tickets want to buy
    ticketClerkMsgLock[myTicketClerk]->Acquire();
    tickets_num[myTicketClerk]=current->groupSize;
	printf("Customer [%d] in Group [%d] is walking up to TicketClerk[%d] to buy [%d] tickets\n", current->customerNumber, current->groupNumber, myTicketClerk, tickets_num[myTicketClerk]);
    ticketclerkMsg_cv[myTicketClerk]->Signal(ticketClerkMsgLock[myTicketClerk]);
    ticketclerkMsg_cv[myTicketClerk]->Wait(ticketClerkMsgLock[myTicketClerk]);
    ticketClerkMsgLock[myTicketClerk]->Release();
    //passes number of tickets & wake up clerk //there's a global number for each clerk int numberOfTicketsForTC[MAX_TC]
    //step 4: get the total money from the ticketclerk
    DBGPRINT(DEBUG_TRACE, "group %d, headcustomer pay the money %d for the tickets\n", current->groupNumber,tickets_money[myTicketClerk]);
	printf("Customer [%d] in Group [%d] in TicketClerk line [%d] is paying [%d] for tickets\n", current->customerNumber, current->groupNumber, myTicketClerk, tickets_money[myTicketClerk]);
	printf("Customer [%d] in Group [%d] is leaving TicketClerk[%d]\n", current->customerNumber, current->groupNumber, myTicketClerk);

//step b: buy some food
    //step 1: ask the members whether buy popcorn
    //step 2; ask the members whether buy soda
    int popcorn_no=0;
    int soda=0;
    //step 3: choose a food clerk, the shortest one
    //right here we need to know, how many do the  cutomer want for popcorn and soda
    customerLock[current->groupNumber]->Acquire();
    DBGPRINT(DEBUG_INFO,  "group  %d get\n", current->groupNumber);
    //signal and wait here for the customer to know how many they want
    customer_cv[current->groupNumber]->Broadcast(customerLock[current->groupNumber]);
    DBGPRINT(DEBUG_INFO,  "group  %d get\n", current->groupNumber);
    customerLock[current->groupNumber]->Release();
    for(int i=0; i<(current->groupSize-1); i++)
    {
        customer_sem[current->groupNumber]->P();
    }
	printf("HeadCustomer [%d] of group [%d] has told the group to proceed.\n", current->customerNumber, current->groupNumber);
    //we only do buy the food if anyone need
    if(customer_popcorn[current->groupNumber]!=0||customer_soda[current->groupNumber]!=0)
    {
find_foodclerk:
        concessClerkLineLock->Acquire();
        int myfoodClerk=find_shortest_line(concessclrk_cus_count,concessclrk_off,numConcessionClerks);
        concessclrk_cus_count [myfoodClerk]++;
        if(concessclrk_cus_count [myfoodClerk]>1)//I need to wait if there is more than me
        {
            DBGPRINT(DEBUG_INFO, "headcustomer  %d wait in the line %d for the food\n",current->customerNumber,myfoodClerk);
			printf("Customer [%d] in Group [%d] is getting in ConcessionClerk line [%d]", current->customerNumber, current->groupNumber, myfoodClerk);
            concessclerkline_cv[myfoodClerk]->Wait(concessClerkLineLock);
            if(OFF_WORK==concessclrk_off[myfoodClerk]||ADD_NEWLINE==foodclrk_addNewline[myfoodClerk])
            {
                DBGPRINT(DEBUG_INFO, "headcustomer  %d is told the line %d is off or add new line, need to change\n", current->customerNumber,myfoodClerk);
				printf("Customer [%d] in Group [%d] sees ConcessionClerk [%d] is on break.\n", current->customerNumber, current->groupNumber, myfoodClerk);
                concessClerkLineLock->Release();
                concessclrk_cus_count [myfoodClerk]--;
                goto find_foodclerk;
            }
        }
        concessClerkLineLock->Release();
        DBGPRINT(DEBUG_INFO,  "group  %d get in the line %d for the food\n", current->groupNumber,  myfoodClerk);
        concessClerkMsgLock[myfoodClerk]->Acquire();
        //step 4: tell how many popcorn and soda wants
        //customer_foodLock[current->groupNumber]->Acquire();
        popcorn_num[myfoodClerk]=customer_popcorn[current->groupNumber];
        soda_num[myfoodClerk]=customer_soda[current->groupNumber];
		printf("Customer [%d] in Group [%d] is walking up to ConcessionClerk[%d] to buy [%d] popcorn and [%d] soda.\n", current->customerNumber, current->groupNumber, myfoodClerk, popcorn_num[myfoodClerk], soda_num[myfoodClerk]);
        //customer_foodLock[current->groupNumber]->Release();
        DBGPRINT(DEBUG_INFO,  "group  %d get\n", current->groupNumber);
        concessclerkMsg_cv[myfoodClerk]->Signal(concessClerkMsgLock[myfoodClerk]);
        DBGPRINT(DEBUG_INFO,  "group  %d get\n", current->groupNumber);
        //step 5: get the total money from the food clerk
        concessclerkMsg_cv[myfoodClerk]->Wait(concessClerkMsgLock[myfoodClerk]);
        concessClerkMsgLock[myfoodClerk]->Release();
        DBGPRINT(DEBUG_TRACE, "group %d, headcustomer pay the money %d for popcorn and soda\n", current->groupNumber,food_money[myfoodClerk]);
		printf("Customer [%d] in Group [%d] in ConcessionClerk line [%d] is paying [%d] for food\n", current->customerNumber, current->groupNumber, myfoodClerk, food_money[myfoodClerk]);
		printf("Customer [%d] in Group [%d] is leaving ConcessionClerk[%d]\n", current->customerNumber, current->groupNumber, myfoodClerk);
    }
    //step c: choose a tickettaker to hand the ticket
find_tictaker:
    DBGPRINT(DEBUG_TRACE, "group %d, goto for tickettaker\n", current->groupNumber);
    ticketTakerLineLock->Acquire();
    DBGPRINT(DEBUG_TRACE, "group %d, goto for tickettaker\n", current->groupNumber);
    //see if any ticketClerk not busy
    int mytictaker=find_shortest_line(ticketTaker_cus_count,ticketTaker_off, numTicketTakers);
    DBGPRINT(DEBUG_TRACE, "group %d, goto line %d for tickettaker\n", current->groupNumber,mytictaker);
    ticketTaker_cus_count [mytictaker]++;
    DBGPRINT(DEBUG_TRACE, "group %d, goto line %d for tickettaker\n", current->groupNumber,mytictaker);
    DBGPRINT(DEBUG_TRACE, "group %d, headcustomer go to taker line %d to enter the room\n", current->groupNumber,mytictaker);
    //step c: choose a tickettaker to hand the ticket
getseats:

    if(ticketTaker_cus_count [mytictaker]>1)//I need to wait if there is more than me
    {
        DBGPRINT(DEBUG_INFO, "headcustomer  %d wait in the line for the taker %d\n",current->groupNumber,mytictaker);
		printf("Customer [%d] in Group [%d] is getting in TicketTaker line [%d]\n", current->customerNumber, current->groupNumber, mytictaker);
        ticketTakerline_cv[mytictaker]->Wait(ticketTakerLineLock);
        DBGPRINT(DEBUG_INFO, "headcustomer %d  is waked up by the tickeclerk %d\n",current->groupNumber,mytictaker);
        if(OFF_WORK==ticketTaker_off[mytictaker])
        {
            DBGPRINT(DEBUG_INFO, "headcustomer  %d is told the line %d is off, need to change\n", current->customerNumber,mytictaker);
			printf("Customer [%d] in Group [%d] sees TicketTaker [%d] is on break.\n", current->customerNumber, current->groupNumber, mytictaker);
            ticketTakerLineLock->Release();
            ticketTaker_cus_count[mytictaker]--;
            ticketTakerLineLock->Release();
            goto find_tictaker;
        }
    }
	
    DBGPRINT(DEBUG_TRACE, "group %d, goto line %d for tickettaker\n", current->groupNumber,mytictaker);
    ticketTakerLineLock->Release();
    DBGPRINT(DEBUG_TRACE, "group %d, goto line %d for tickettaker\n", current->groupNumber,mytictaker);

    //step 2: give the ticket
    ticketTakerMsgLock[mytictaker]->Acquire();
    DBGPRINT(DEBUG_TRACE, "group %d, goto line %d for tickettaker\n", current->groupNumber,mytictaker);
    tictaker_tic_number[mytictaker]=current->groupSize;
    DBGPRINT(DEBUG_TRACE, "group %d, goto line %d for tickettaker\n", current->groupNumber,mytictaker);
    DBGPRINT(DEBUG_TRACE, "group %d wants %d seats from %d \n", current->groupNumber,current->groupSize,mytictaker);
	printf("Customer [%d] in Group [%d] is walking up to TicketTaker[%d] to give [%d] tickets.\n", current->customerNumber, current->groupNumber, mytictaker, tictaker_tic_number[mytictaker]);
    ticketTakerMsg_cv[mytictaker]->Signal(ticketTakerMsgLock[mytictaker]);
    DBGPRINT(DEBUG_TRACE, "group %d, goto line %d for tickettaker\n", current->groupNumber,mytictaker);
    ticketTakerMsg_cv[mytictaker]->Wait(ticketTakerMsgLock[mytictaker]);
    DBGPRINT(DEBUG_TRACE, "group %d, goto line %d for tickettaker\n", current->groupNumber,mytictaker);
    ticketTakerMsgLock[mytictaker]->Release();
    DBGPRINT(DEBUG_TRACE, "group %d, goto line %d for tickettaker\n", current->groupNumber,mytictaker);
	printf("Customer [%d] in Group [%d] is leaving TicketTaker[%d]\n", current->customerNumber, current->groupNumber, mytictaker);
    //step 3: get the result
    if(SEATS_GOT==seats_result[mytictaker])
    {
		for (int i = 0; i < current->groupSize; i++)
			printf("Customer [%d] in Group [%d] is leaving the lobby.\n", current->customerNumber + i, current->groupNumber);
        DBGPRINT(DEBUG_TRACE, "group %d  sit from %d to %d in room %d\n", current->groupNumber, seats_from[mytictaker], seats_to[mytictaker],room_number[mytictaker]);
        //right here , we get the seats , I should decide who sits where
        DBGPRINT(DEBUG_TRACE, "the head %d in group %d  sit in %d in room %d\n",current->customerNumber, current->groupNumber, seats_from[mytictaker], room_number[mytictaker]);
        customer_seatno[current->groupNumber]=seats_from[mytictaker]+1;
        DBGPRINT(DEBUG_TRACE, "i am the head %d in group %d \n", current->customerNumber,current->groupNumber);
        customerLock[current->groupNumber]->Acquire();
        //signal and wait here for the customer to know how many they want
        DBGPRINT(DEBUG_TRACE, "i am the head %d in group %d \n", current->customerNumber,current->groupNumber);
        customer_cv[current->groupNumber]->Broadcast(customerLock[current->groupNumber]);
        DBGPRINT(DEBUG_TRACE, "i am the head %d in group %d \n", current->customerNumber,current->groupNumber);
        customerLock[current->groupNumber]->Release();
        DBGPRINT(DEBUG_TRACE, "i am the head %d in group %d \n", current->customerNumber,current->groupNumber);
        for(int i=0; i<(current->groupSize-1); i++)
        {
            customer_sem[current->groupNumber]->P();
        }
    }
//   else if(seats_result[mytictaker]==SEATS_FAILED)//have not find a seats, need to wait in the taker again
    else if(SEATS_FAILED==seats_result[mytictaker])
    {
        DBGPRINT(DEBUG_TRACE, "group %d has to wait cause there are no enough seats\n", current->groupNumber);
		for (int i = 0; i < current->groupSize; i++)
			printf("Customer [%d] in Group [%d] is in the lobby.\n", current->customerNumber + i, current->groupNumber);
        goto getseats;
    }
    else//the taker need to break, find another one
    {
        DBGPRINT(DEBUG_TRACE, "group % has to choose another taker cause this one would break\n", current->groupNumber);
        ticketTakerLineLock->Acquire();
        ticketTaker_cus_count[mytictaker]--;
        ticketTakerLineLock->Release();
        goto find_tictaker;
    }
	for(int i=0; i<(current->groupSize-1); i++)
		{
			printf("Customer [%d] in Group [%d] has found the following seat: [%d]\n", current->customerNumber,current->groupNumber, seats_from[mytictaker] + i);
		}
    DBGPRINT(DEBUG_TRACE, "group %d, goto line %d for tickettaker\n", current->groupNumber,mytictaker);
    //step d:watch the movie over
    DBGPRINT(DEBUG_TRACE, "group %d, goto line %d for tickettaker\n"
             , current->groupNumber,mytictaker);
    movie_stateLock->Acquire();
    movieState_cv->Wait(movie_stateLock);
    movie_stateLock->Release();
    DBGPRINT(DEBUG_TRACE, "MOVIE is over, group %d is going to leave\n", current->groupNumber);
    customerLock[current->groupNumber]->Acquire();
    //signal and wait here for the customer to ask whether go to the restroom or not
    customer_cv[current->groupNumber]->Broadcast(customerLock[current->groupNumber]);
    customerLock[current->groupNumber]->Release();
    DBGPRINT(DEBUG_TRACE, "MOVIE is over, group %d is going to leave\n", current->groupNumber);
    for(int i=0; i<(current->groupSize-1); i++)
    {
        customer_sem[current->groupNumber]->P();
    }
    DBGPRINT(DEBUG_TRACE, "MOVIE is over, group %d is checking whether go to bathroom\n", current->groupNumber);
    //right now, we check anyone want to go to the bathroom, if we have to wait for him
    if(1==customer_needbath[current->groupNumber])
    {
        DBGPRINT(DEBUG_TRACE, "MOVIE is over, group %d customer_needbath\n", current->groupNumber);
        customerLock[current->groupNumber]->Acquire();
        //signal and wait here for the customer to know how many they want
        customer_cv[current->groupNumber]->Broadcast(customerLock[current->groupNumber]);
        customerLock[current->groupNumber]->Release();
    }
    DBGPRINT(DEBUG_TRACE, "customer %d in group %d is going to leave\n",current->customerNumber, current->groupNumber);
	printf("Customer [%d] in Group [%d] has left the movie theater\n", current->customerNumber, current->groupNumber);
    //step e: call all the members of the group to get out of the theater
	numGroupsRemaining--;
}


//the manager told the ticketclerk to work
void regularCustomer_startWork(int cus)
{
    CustomerData *current=(CustomerData *)cus;
    DBGPRINT(DEBUG_TRACE, "regularCustomer_startWork, customer %d in group %d starts \n",current->customerNumber,current->groupNumber);

    //step 1: first, we need to wait  for the head customer to tell him I need popcorn and soda
    customerLock[current->groupNumber]->Acquire();
    DBGPRINT(DEBUG_TRACE, "regularCustomer_startWork, customer %d in group %d starts \n",current->customerNumber,current->groupNumber);
    customer_cv[current->groupNumber]->Wait(customerLock[current->groupNumber]);
    //righ now, we random to know whether we need popcorn or the soda
    customer_foodLock[current->groupNumber]->Acquire();
    if((rand()%4)!=0)
    {
        customer_popcorn[current->groupNumber]=1;
    }
    DBGPRINT(DEBUG_TRACE, "customer_popcorn[current->groupNumber] %d customer %d in group %d starts \n",customer_popcorn[current->groupNumber],current->customerNumber,current->groupNumber);
    if((rand()%4)!=0)
    {
        customer_soda[current->groupNumber]=1;
    }
	printf("Customer [%d] in group [%d] wants [%d] popcorn and [%d] soda.\n", current->customerNumber, current->groupNumber, customer_popcorn[current->groupNumber], customer_soda[current->groupNumber]);
    customer_foodLock[current->groupNumber]->Release();
    customer_sem[current->groupNumber]->V();
    //step 2: we wait here for the customer to tell me where to sit
    DBGPRINT(DEBUG_TRACE, "regularCustomer_startWork, customer %d in group %d starts \n",current->customerNumber,current->groupNumber);
	printf("Customer [%d] of group [%d] is waiting for the HeadCustomer.\n",current->customerNumber,current->groupNumber);
    customer_cv[current->groupNumber]->Wait(customerLock[current->groupNumber]);
    DBGPRINT(DEBUG_TRACE, "regularCustomer_startWork, customer %d in group %d starts \n",current->customerNumber,current->groupNumber);
    customer_seatLock[current->groupNumber]->Acquire();
	printf("Customer [%d] of group [%d] has been told by the HeadCustomer to proceed.\n", current->customerNumber,current->groupNumber);
    //DBGPRINT(DEBUG_TRACE, "customer %d in group %d would sit on the seat %d to watch movie \n",current->customerNumber,current->groupNumber, customer_seatno[current->groupNumber]++);
	printf("Customer [%d] in group [%d] is sitting in a theater room seat.\n", current->customerNumber, current->groupNumber); 
    customer_seatLock[current->groupNumber]->Release();
    customer_sem[current->groupNumber]->V();
    //step 3: wait for the head to wake me up, the movei is boring
    customer_cv[current->groupNumber]->Wait(customerLock[current->groupNumber]);
    DBGPRINT(DEBUG_TRACE, "movie is over, customer %d in group %d would decide whether to the bathroom \n",current->customerNumber,current->groupNumber);
	printf("Customer [%d] in group [%d] is getting out of a theater room seat.\n", current->customerNumber,current->groupNumber);
    //right now we need to know wether I need to goto the bathroom
    if((rand()%4)==0)
    {
        DBGPRINT(DEBUG_TRACE, "sorry, customer %d in group %d would go to the bathroom \n",current->customerNumber, current->groupNumber);
		printf("Customer [%d] in Group [%d] is going to the bathroom.\n", current->customerNumber, current->groupNumber);
        customer_needbath[current->groupNumber]=1;
		printf("Customer [%d] in Group [%d] is leaving the bathroom.\n", current->customerNumber, current->groupNumber);
    }
    customer_sem[current->groupNumber]->V();
    DBGPRINT(DEBUG_TRACE, "regularCustomer_startWork, customer %d in group %d starts \n",current->customerNumber,current->groupNumber);
    //step 5: if anyone need to go to the bathroom, we need to wait here until the head told he is over
    customer_cv[current->groupNumber]->Wait(customerLock[current->groupNumber]);
    //now we leave the movie theater
    DBGPRINT(DEBUG_TRACE, "customer %d in group %d would leave the room\n",current->customerNumber, current->groupNumber);
};



